The behavior I'm looking to implement would wrap each word the user types in <div class="word"></div> as they type their message. The parent div they're typing into has contenteditable="true".
One complicating factor is that for this use case one "word" div may contain two words (imagine a name would be considered one "word" so e.g. <div class="word">Bob Smith</div> may occur). This means I can't just grab all of the textContent when the user presses space and split(" ") into an array to build the div.word DOM elements.
I'm thinking when the user presses space I can get all the child nodes of the contenteditable div and loop through them to check which is a textNode and which is not (i.e. a word already wrapped in div.word). Then for the text nodes only I can build a div.word DOM element and append all of these to the contenteditable div.
I hope that's clear. Here's sort of where I'm at:
<div id="editor" contenteditable></div>
#editor {
border: 1px solid #333;
padding: 10px;
}
#editor .word {
background: yellow;
display: inline-block;
}
const editorElement = document.getElementById('editor');
function handleSpacebarPress() {
// Get all editor child nodes
let editorChildNodes = [...editor.childNodes];
// Clear editor of all child nodes
editor.innerHTML = '';
editorChildNodes.forEach(node => {
// If node is a text node (not a div.word element)
if (node.nodeType === 3) {
// Create div.word
let wordDiv = document.createElement('div');
wordDiv.className = 'word';
wordDiv.textContent = node.textContent;
editor.appendChild(wordDiv);
}
// Else node is already a div.word element
else {
editor.appendChild(node);
}
});
// Return caret to end of editor
const editorLength = editor.childNodes.length;
const lastNode = editor.childNodes[editorLength - 1]; // Last editor node
const range = document.createRange();
const selection = window.getSelection();
range.setStart(lastNode, 1);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
editorElement.addEventListener('keydown', e => {
if (e.code === 'Space') {
handleSpacebarPress();
}
});
You can view this on jsfiddle here.
Right now it seems to just place all words directly into the one div.word element and I can't seem to handle creating a new one for each word.
Any ideas?
I guess you want to achieve something like this:
Note how the spaces between the words are not highlighted and therefore have to be inside the main editor. You could fix this (as you pointed out in the comments) by adding spaces after each word in the editor and trim()-ming each word so that there are no spaces at the start or end of inner nodes.
You might to rethink your code, because it has some disadvantages.
handleSpacebarPressIf you want to program the editor yourself, I suggest you use a function highlightWords that gets triggered after every change event (maybe with some artificial delay). In that function you can use a regular expression to find all the words according to your criteria and replace them with <span class=word>$1</span>.